package zack.project.config;

import zack.project.types.annotation.DCCValue;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;

import org.apache.curator.framework.recipes.cache.TreeCache;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**动态配置中心
 * @author A1793
 */
@Slf4j
@Configuration
public class  DCCValueBeanFactory implements BeanPostProcessor {


    private static final String BASE_CONFIG_PATH = "/big-market-dcc";

    private static final String BASE_CONFIG_PATH_CONFIG = BASE_CONFIG_PATH + "/config";

    private final CuratorFramework client;

    private Map<String, Object> dccObjGroup = new HashMap<>();

    public DCCValueBeanFactory(CuratorFramework client) throws Exception {
        this.client = client;

        if(null == client.checkExists().forPath(BASE_CONFIG_PATH_CONFIG)) {
            client.create().creatingParentsIfNeeded().forPath(BASE_CONFIG_PATH_CONFIG);
            log.info("DCC 节点监听 base node {} not absent create new done!", BASE_CONFIG_PATH_CONFIG);

        }
        //监听zookeeper动态配置中心路径{"big-market-dcc/config"}下的所有节点
        TreeCache curatorCache = new TreeCache(client, BASE_CONFIG_PATH_CONFIG);
        //将zookeeper中的缓存同步到本地
        curatorCache.start();
        //监听curatorCache并添加监听器，当BASE_CONFIG_PATH_CONFIG路径下节点发生变化时监听器被调用
        curatorCache.getListenable().addListener((type,event) ->{
        switch (event.getType()){
            case NODE_UPDATED:{
                //获取配置中心中属性值更新的路径，用于在本地缓存dccObjGroup中快速获取对应bean对象和需要更新的属性
                String dccValuePath = event.getData().getPath();
                Object objbean = dccObjGroup.get(dccValuePath);

                if(null == objbean){return;}
                Class<?> objbeanClass = objbean.getClass();
                try {
                        if(AopUtils.isAopProxy(objbeanClass)){
                             objbeanClass = AopUtils.getTargetClass(objbean);
                        }
                        //配置中心中存储的属性值对应的key:big-market-dcc/config/#{字段名}，
                    // 通过下面的操作可以获得bean对象中对应的字段
                        Field field = objbeanClass.getDeclaredField(dccValuePath
                                .substring(dccValuePath.lastIndexOf("/") + 1));
                        field.setAccessible(true);
                        //根据配置中心中更新的属性值更新本地的属性值
                        field.set(objbean,new String(event.getData().getData()));
                        field.setAccessible(false);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            ;break;
            default:break;
        }
        });


    }

    //DCCValueBeanFactory实现了BeanPostProcessor接口，当容器初始化阶段，
    // 有对象被初始化化后会调用postProcessAfterInitialization
    // 在zookeeper里获得某个类里标注了@DCCValue注解的属性，设置其属性值，保存到本地缓存并上报到动态配置中心
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        Class<?> tarBeanClass = bean.getClass();
        Object tarBeanObj = bean;
        if(AopUtils.isAopProxy(bean)){
            tarBeanClass = AopUtils.getTargetClass(bean);
            tarBeanObj = AopProxyUtils.getSingletonTarget(bean);
        }
        //获取当前对象的全部字段
        Field[] fields = tarBeanClass.getDeclaredFields();
        //循环过滤出有@DCCValue注解的字段
        for (Field field : fields) {
            if (!field.isAnnotationPresent(DCCValue.class)) {
                continue;
            }
            DCCValue dccValue = field.getAnnotation(DCCValue.class);
            String value = dccValue.value();
            if (StringUtils.isBlank(value)) {
                throw new RuntimeException(field.getName() + " @DCCValue is not config value config case 「isSwitch/isSwitch:1」");
            }
            //解析属性上的@DCCVlue注解
            String[] split = value.split(":");
            String key = split[0];
            String defaultValue = split.length == 2 ? split[1] : null;

            try {
                //构建zookeeper配置中心的配置路径
                String keyPath = BASE_CONFIG_PATH_CONFIG.concat("/").concat(key);
                //如果配置中心没有该属性的对应配置则将该属性的值设置为注解配置的值，
                // 并向配置中心注册(cacheKey:big-market-config-#{字段名})该属性的空值路径
                if (client.checkExists().forPath(keyPath) == null) {
                    client.create().creatingParentsIfNeeded().forPath(keyPath);
                    if (StringUtils.isNotBlank(defaultValue)) {
                        field.setAccessible(true);
                        field.set(tarBeanObj, defaultValue);
                        field.setAccessible(false);
                    }
                    log.info("DCC 节点监听 创建节点 {}", keyPath);
                } else {
                    //配置中心存在关于该字段值的配置，则用该配置为字段赋值
                    if(StringUtils.isNotBlank(defaultValue)){
                        //获取zookeeper中对应路径的ZNode的Data域数据
                        String configValue = new String(client.getData().forPath(keyPath));
                        field.setAccessible(true);
                        field.set(tarBeanObj, configValue);
                        field.setAccessible(false);
                        log.info("DCC 节点监听 设置配置 {} {} {}", keyPath, field.getName(), configValue);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            //向本地动态配置缓存中存入配置中心中该属性的路径和有动态配置需求的bean对象，
            // 当动态配置监听器监听到配置中心有对应属性值的更新时可以在本地快速更新对应的属性值
            dccObjGroup.put(BASE_CONFIG_PATH_CONFIG.concat("/").concat(key), tarBeanObj);
        }
        return bean;
    }
}
